package cn.xhh.dailyreview.server;

import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;

import cn.xhh.dailyreview.client.Service;
import cn.xhh.dailyreview.client.utils.Result;
import cn.xhh.dailyreview.client.utils.Token;
import cn.xhh.dailyreview.client.utils.Result.Status;
import cn.xhh.dailyreview.server.dao.UserDAO;
import cn.xhh.dailyreview.server.model.User;
import cn.xhh.dailyreview.server.utils.Utility;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class ServiceImpl extends RemoteServiceServlet implements Service {

	private static final Logger log = Logger.getLogger(ServiceImpl.class.getName());
	
	private static final String LOGIN_KEY_UESR_ID = "login_user_ID";
	private static final String LOGIN_KEY_UESR_EMAIL = "login_user_email";
	private static final String LOGIN_KEY_UESR_LEVEL = "login_user_level";;
	
	private static final String WEB_URL = "http://daily-review.appspot.com/";
	
	private static final long serialVersionUID = -4503749260950101391L;


	private UserDAO userDao = new UserDAO();

	@Override
	public Result login(String email, String encrPasswd) {
		if (StringUtils.isBlank(email) || StringUtils.isBlank(encrPasswd)) {
			log.warning("Login failed: blank email or password");
			return Result.errServer();
		}
		
		Result result = new Result();
		
		List<User> users = userDao.read(email, encrPasswd);
		if (users == null) { // query user failed
			result.status = Status.ERR_SERVER;
		} else if (users.isEmpty()) {
			log.info("Login failed: incorrect email or password. Email: " + email + ipInfo());
			result.status = Status.FAILURE;
		} else {
			if (users.size() > 1) // error to notice: more than one users have the same email
				log.severe("Multiple users found with the same email: " + email
						+ ", count: " + users.size());
			User user = users.get(0);
			if (user.isConfirmed()) { // success
				storeLoginInfo(user.getId(), user.getEmail(), user.getPrivilege());
				log.info("User signed in. Email: " + user.getEmail() + ipInfo());
			} else if (StringUtils.isBlank(user.getConfirmStr())){ // user restricted
				log.info("Login failed: user restricted. Email: " + email + ipInfo());
				result.status = Status.OTHER_A;
			} else { // user not confirmed yet
				result.status = Status.OTHER_B;
			}
		}
		
		return result;
	}

	@Override
	public Result logout() {
		HttpSession session = getThreadLocalRequest().getSession(false);
		if (session != null)
			session.invalidate();
		
		return Result.success();
	}
	
	@Override
	public Result checkLogin() {
		String userEmail = getUserEmail();
		Result result;
		
		if (userEmail == null)
			result = Result.failure();
		else {
			result = Result.success();
			result.addValue("email", userEmail);
			result.addValue("userLevel", String.valueOf(getUserLevel()));
		}
		
		return result;
	}
	
	@Override
	public boolean saveReview(List<String> reviews) {
		if (checkLogin().unsuccessful())
			return false;
		
		// TODO to be implemented
		
		if (log.isLoggable(Level.FINE))
			log.fine("Review saved for user: " + getUserEmail());
		return true;
	}
	
	@Override
	public Result submitRegistration(String email, boolean force) {
		if (email == null) {
			log.warning("Submitting registration failed: null email");
			return Result.errServer();
		}
		
		List<User> users = userDao.read(email);
		if (users == null)
			return Result.errServer();
		
		User user;
		if (!users.isEmpty()) { // user found with the email
			if (users.size() > 1) // error to notice: more than one users have the same email
				log.severe("Multiple users found with the same email: " + email
						+ ", count: " + users.size());
			user = users.get(0);
			// email is used by an confirm user,
			// or the user is inactive (confirmed is false and has no confirmStr)
			if (user.isConfirmed() || StringUtils.isBlank(user.getConfirmStr()))
				return Result.valuesExist();
			else if (!force) // email submitted before, notify the user
				return Result.failure();
			else // email submitted before, however, re-submit now, generate a new code
				user.setConfirmStr(Utility.genConfirmString());
		} else { // user not exist, create one
			user = new User(email);
		}

		// update the user for confirmation, and send email notification
		if (userDao.createOrUpdate(user) && mailRegistration(email, user.getConfirmStr()))
			return Result.success();
		else
			return Result.errServer();
	}
	
	@Override
	public Result submitForgotPasswd(String email) {
		if (email == null) {
			log.warning("Submitting Forgot Password failed: null email");
			return Result.errServer();
		}
		
		List<User> users = userDao.read(email);
		if (users == null)
			return Result.errServer();
		
		Result result = new Result();
		
		if (users.isEmpty()) { // user does not exist
			result.status = Status.FAILURE;
		} else {
			if (users.size() > 1) // error to notice: more than one users have the same email
				log.severe("Multiple users found with the same email: " + email
						+ ", count: " + users.size());
			User user = users.get(0);
			if (!user.isConfirmed() && StringUtils.isBlank(user.getConfirmStr())) { // user restricted
				result.status = Status.OTHER_A;
			} else {
				user.setConfirmStr(Utility.genConfirmString());
				if (userDao.createOrUpdate(user)) { // code generated, send mail
					// user active, or new (not confirmed yet)
					boolean mailSent = user.isConfirmed() ?
							mailForgetPasswd(email, user.getConfirmStr()) :
							mailRegistration(email, user.getConfirmStr());
					if (!mailSent)
						result.status = Status.ERR_SERVER;
				} else {
					result.status = Status.ERR_SERVER;
				}
			}
		}
		
		return result;
	}

	@Override
	public Result checkSetup(String email, String code) {
		if (email==null || code==null) {
			log.warning("Checking confirmation failed: null email or code");
			return Result.errServer();
		}
		
		List<User> users = userDao.read(email);
		if (users == null) // query user failed
			return Result.errServer();
		
		Result result = new Result();
		
		if (users.isEmpty()) { // user not exist: maybe deleted for confirming too late
			log.info("Checking confirmation failed - user not exist: " + email + ipInfo());
			result.status = Status.OTHER_A;
		} else {
			if (users.size() > 1) // error to notice: more than one users have the same email
				log.severe("Multiple users found with the same email: " + email
						+ ", count: " + users.size());
			User user = users.get(0);
			if (StringUtils.isBlank(user.getConfirmStr()) 
					|| !code.equals(user.getConfirmStr())) { // user restricted or code is incorrect
				log.info("Checking confirmation failed - user restricted or incorrect code: " 
						+ code + ". Email: " + email + ipInfo());
				result.status = Status.FAILURE;
			} // otherwise: success
		}

		return result;
	}

	@Override
	public Result setupUser(String email, String encrPwd) {
		if (email==null || encrPwd==null) {
			log.warning("Confirming user failed: null email or password");
			return Result.errServer();
		}
		
		List<User> users = userDao.read(email);
		if (users == null) // query user failed
			return Result.errServer();

		Result result = new Result();
		
		if (users.isEmpty()) { // user not exist
			log.warning("Confirming user failed: user not exist. This should not "
					+ "have happened. Email + " + email + ipInfo());
			result.status = Status.FAILURE;
		} else {
			if (users.size() > 1) // error to notice: more than one users have the same email
				log.severe("Multiple users found with the same email: " + email
						+ ", count: " + users.size());
			User user = users.get(0);
			if (StringUtils.isBlank(user.getConfirmStr())) { // user restricted
				log.warning("Confirming user failed: user restricted. This should not "
						+ "have happened. Email + " + email + ipInfo());
				result.status = Status.OTHER_A;
			} else {
				user.setConfirmed(true);
				user.setConfirmStr(null);
				user.setPassword(encrPwd);
				if (!userDao.createOrUpdate(user)) // updating user failed
					result.status = Status.ERR_SERVER;
				else // success, login for the user
					storeLoginInfo(user.getId(), user.getEmail(), user.getPrivilege());
			}
		}
		
		return result;
	}
	
	@Override
	public String getUserEmail() {
		return (String) getFromSession(LOGIN_KEY_UESR_EMAIL);
	}

	/* ------------------------- private methods ------------------------------------------ */

	/**
	 * @return <code>true</code>: mail sent successfully
	 */
	private boolean mailRegistration(String email, String code) {
		String subject = "Daily Review - Please confirm your registration";
		String link = WEB_URL + "#action=" + Token.confirm
				+ "&email=" + email + "&code=" + code;
		String msgBody = "Hi, " + email + "\n\n"
			+ "Welcome to Daily Review.\n"
			+ "Click the following link to complete your registration\n"
			+ "(or copy and paste the link to your browser):\n"
			+ link + "\n\n"
			+ "If you haven't submitted such a registration, "
			+ "please ignore this email or choose\n"
			+ "\"Cancel Registration\" in the page of the above link.\n\n"
			+ "Best wishes.\n" + WEB_URL + "\n";
		
		try {
			Utility.sendMail(email, subject, msgBody);
		} catch (Exception e) {
			log.warning(Utility.buildLog("Sending mail failed", e));
			return false;
		}
		
		return true;
	}

	/**
	 * @return <code>true</code>: mail sent successfully
	 */
	private boolean mailForgetPasswd(String email, String code) {
		String subject = "Daily Review - Reset your login password";
		String link = WEB_URL + "#action=" + Token.reset_pwd
				+ "&email=" + email + "&code=" + code;
		String msgBody = "Hi, " + email + "\n\n"
			+ "Welcome to Daily Review.\n"
			+ "Click the following link to reset your login password\n"
			+ "(or copy and paste the link to your browser):\n"
			+ link + "\n\n"
			+ "Best wishes.\n" + WEB_URL + "\n";
		
		try {
			Utility.sendMail(email, subject, msgBody);
		} catch (Exception e) {
			log.warning(Utility.buildLog("Sending mail failed", e));
			return false;
		}
		
		return true;
	}

	/**
	 * Store the id and email of the currently logged-in user into session.
	 * @param userLevel 
	 */
	private void storeLoginInfo(Long id, String email, int userLevel) {
		HttpSession session = getThreadLocalRequest().getSession(true);
		if (session == null) {
			log.severe("Storing login info failed: could not get HttpSession");
		} else {
			session.setAttribute(LOGIN_KEY_UESR_ID, id);
			session.setAttribute(LOGIN_KEY_UESR_EMAIL, email);
		}
	}

	/**
	 * @return the value associated with the key stored in the HttpSession
	 */
	private Object getFromSession(String key) {
		HttpSession session = getThreadLocalRequest().getSession(false);
		return (session == null) ? null : session.getAttribute(key);
	}
	
	/**
	 * Get the user level: <br/>
	 * -1 for guest,<br/>
	 * 0 for normal user,<br/>
	 * 9 for administrator.
	 * @return the user level of the current user (stored in the HttpSession)
	 */
	private int getUserLevel() {
		Integer level = (Integer) getFromSession(LOGIN_KEY_UESR_LEVEL);
		return (level == null) ? -1 : level.intValue();
	}
	
	/**
	 * @return the IP address of the client who sent the requests
	 */
	private String getIp() {
		final String UNKNOWN_TOKEN = "unknown";
		
		HttpServletRequest request = getThreadLocalRequest();
		if (request == null) return UNKNOWN_TOKEN;
		
		String ip = request.getHeader("x-forwarded-for");
		if(StringUtils.isBlank(ip) || UNKNOWN_TOKEN.equalsIgnoreCase(ip))
			ip = request.getHeader("Proxy-Client-IP");
		if(StringUtils.isBlank(ip)  || UNKNOWN_TOKEN.equalsIgnoreCase(ip))
			ip = request.getHeader("WL-Proxy-Client-IP");
		if(StringUtils.isBlank(ip)  || UNKNOWN_TOKEN.equalsIgnoreCase(ip))
			ip = request.getRemoteAddr();
		
		return ip;
	}

	private String ipInfo() {
		return " (IP: " + getIp() + ")";
	}
	
}

